package nl.unimaas.games.tron.player.mcts;

import java.util.ArrayList;

import nl.unimaas.games.tron.engine.Board;
import nl.unimaas.games.tron.util.KnuthRandom;
import nl.unimaas.games.tron.util.LGRTables;
import nl.unimaas.games.tron.util.NGram;

public interface SolverSelectionStrategy {

    public SolverNode getNode(SolverNode node);
    public final static SolverSelectionStrategy RANDOM = new SolverSelectionStrategy() {

        @Override
        public String toString() {
            return "random";
        }

        @Override
        public SolverNode getNode(SolverNode node) {
            ArrayList<SolverNode> candidates = new ArrayList<SolverNode>();

            for (int i = 0; i < node.children.size(); i++) {
                SolverNode n = (SolverNode) node.children.get(i);
                //if (n.proven)
                //	continue;
                //pruning
                if (node.max) {
                    if (n.pess < node.opti) {
                        candidates.add(n);
                    } else {
                        System.out.println("max skip");
                    }
                } else {
                    if (n.opti > node.pess) {
                        candidates.add(n);
                    } else {
                        System.out.println("min skip");
                    }
                }
                //candidates.add(n);
            }
            if (candidates.isEmpty()) {
                return null;
            } else {
                return candidates.get(KnuthRandom.nextInt(candidates.size()));
            }
        }
    };
    public final static SolverSelectionStrategy UCT = new UCT(10, 30);
    public final static SolverSelectionStrategy SIMPLE_UCT = new SolverSelectionStrategy() {

        private SelectionStrategy uct = new SelectionStrategy.UCT(10, 30);

        @Override
        public SolverNode getNode(SolverNode node) {
            return (SolverNode) uct.getNode(node);
        }

        @Override
        public String toString() {
            return "simpleUCT";
        }
    };

    public static class UCT implements SolverSelectionStrategy {

        protected double C = 10;
        protected int T = 30;

        public UCT(double C, int T) {
            this.C = C;
            this.T = T;
        }

        @Override
        public SolverNode getNode(SolverNode node) {
            SolverNode bestNode = null;
            double bestValue = Double.NEGATIVE_INFINITY;
            double uct;
            for (int i = 0; i < node.children.size(); i++) {
                SolverNode n = (SolverNode) node.children.get(i);
                if (n.proven) {
                    continue;
                }
                //pruning
                if (node.max) {
                    if (n.pess >= node.opti) {
                        continue;
                    }
                } else if (n.opti <= node.pess) {
                    continue;
                }

                uct = n.value / (n.visits + 1.0) + C * Math.sqrt(Math.log(node.visits + 1.0) / (n.visits + 1.0));

                if (uct > bestValue) {
                    bestNode = n;
                    bestValue = uct;
                }
            }
            return bestNode;
        }

        @Override
        public String toString() {
            return "UCTSolve";
        }
    }

    public static class BiasedUCT implements SolverSelectionStrategy {

        protected double C = 10;
        protected int T = 30;
        protected double W = 10;

        public BiasedUCT(double C, int T, double W) {
            this.C = C;
            this.T = T;
            this.W = W;
        }

        @Override
        public SolverNode getNode(SolverNode node) {
            SolverNode bestNode = null;
            double bestValue = Double.NEGATIVE_INFINITY;
            double uct, pb;
            for (int i = 0; i < node.children.size(); i++) {
                SolverNode n = (SolverNode) node.children.get(i);
                if (n.proven) {
                    continue;
                }
                //pruning
                if (node.max) {
                    if (n.pess >= node.opti) {
                        continue;
                    }
                } else if (n.opti <= node.pess) {
                    continue;
                }

                if (node.move != Board.MOVE_NONE) {
                    pb = (W * (Double) n.tag) / (n.visits - n.draws - n.wins + 1.0);
                } else {
                    pb = 0;
                }
                uct = n.value / (n.visits + 1.0) + C * Math.sqrt(Math.log(node.visits + 1.0) / (n.visits + 1.0)) + pb;

                if (uct > bestValue) {
                    bestNode = n;
                    bestValue = uct;
                }
            }
            return bestNode;
        }

        @Override
        public String toString() {
            return "BiasedUCTSolve";
        }
    }

    public static class NGramUCTSelection implements SolverSelectionStrategy {

        private NGram ngram;
        private double C;
        private int T;
        private SolverSelectionStrategy.UCT uct;
        private boolean syncMoves;

        public NGramUCTSelection(NGram ngram, double C, int T, boolean syncMoves) {
            this.ngram = ngram;
            this.C = C;
            this.T = T;
            this.uct = new SolverSelectionStrategy.UCT(C, T);
            this.syncMoves = syncMoves;
        }

        @Override
        public SolverNode getNode(SolverNode node) {
            boolean unvisitedLegalMoves = false;

            for (int i = 0; i < node.children.size(); i++) {
                if (node.children.get(i).visits == 0) {
                    unvisitedLegalMoves = true;
                }
            }

            if (unvisitedLegalMoves) {
                int firstMove = -1, secondMove = -1;
                if (this.syncMoves) {
                    if (node.depth % 2 == 0) {
                        if (node.parent != null) {
                            if (node.parent.parent != null) {
                                secondMove = node.parent.parent.playerPosition;
                                if (node.parent.parent.parent != null) {
                                    firstMove = node.parent.parent.parent.playerPosition;
                                }
                            }

                        }
                    } else {
                        secondMove = node.playerPosition;
                        if (node.parent != null) {
                            if (node.parent.parent != null) {
                                if (node.parent.parent.parent != null) {
                                    firstMove = node.parent.parent.parent.playerPosition;
                                }
                            }
                        }
                    }
                } else {
                    if (node.parent != null) {
                        firstMove = node.parent.playerPosition;
                    }
                    secondMove = node.playerPosition;
                }

                ArrayList<Integer> validMoves = new ArrayList<Integer>();
                for (int i = 0; i < node.children.size(); i++) {
                    if (node.children.get(i).visits == 0) {
                        validMoves.add(node.children.get(i).playerPosition);
                    }
                }

                int move = -1;
                if (firstMove != -1 && secondMove != -1) {
                    move = ngram.getAvgPosition(firstMove, secondMove, validMoves);
                    for (int i = 0; i < node.children.size(); i++) {
                        if (node.children.get(i).playerPosition == move) {
                            return (SolverNode) node.children.get(i);
                        }
                    }
                }
            }

            //If the above did not decide upon which move to make, apply UCT
            return this.uct.getNode(node);
        }

        @Override
        public String toString() {
            return "NGramUCT";
        }
    }

    public static class LGRUCTSelection implements SolverSelectionStrategy {

        private LGRTables tables;
        private double C;
        private int T;
        private SelectionStrategy.UCT uct;
        private boolean syncMoves;

        public LGRUCTSelection(LGRTables tables, double C, int T, boolean syncMoves) {
            this.tables = tables;
            this.C = C;
            this.T = T;
            this.uct = new SelectionStrategy.UCT(C, T);
            this.syncMoves = syncMoves;
        }

        @Override
        public SolverNode getNode(SolverNode node) {
            boolean unvisitedLegalMoves = false;

            for (int i = 0; i < node.children.size(); i++) {
                if (node.children.get(i).visits == 0) {
                    unvisitedLegalMoves = true;
                }
            }

            if (unvisitedLegalMoves) {
                int firstMove = -1, secondMove = -1;
                if (this.syncMoves) {
                    if (node.depth % 2 == 0) {
                        if (node.parent != null) {
                            if (node.parent.parent != null) {
                                secondMove = node.parent.parent.playerPosition;
                                if (node.parent.parent.parent != null) {
                                    firstMove = node.parent.parent.parent.playerPosition;
                                }
                            }

                        }
                    } else {
                        secondMove = node.playerPosition;
                        if (node.parent != null) {
                            if (node.parent.parent != null) {
                                if (node.parent.parent.parent != null) {
                                    firstMove = node.parent.parent.parent.playerPosition;
                                }
                            }
                        }
                    }
                } else {
                    if (node.parent != null) {
                        firstMove = node.parent.playerPosition;
                    }
                    secondMove = node.playerPosition;
                }

                ArrayList<Integer> validMoves = new ArrayList<Integer>();
                for (int i = 0; i < node.children.size(); i++) {
                    if (node.children.get(i).visits == 0) {
                        validMoves.add(node.children.get(i).playerPosition);
                    }
                }

                int move = -1;
                if (firstMove != -1 && secondMove != -1) {
                    move = tables.getLGR2Position(node.children.get(0).playerNr, firstMove, secondMove, validMoves);
                    for (int i = 0; i < node.children.size(); i++) {
                        if (node.children.get(i).playerPosition == move) {
                            return (SolverNode) node.children.get(i);
                        }
                    }
                }
            }

            //If the above did not decide upon which move to make, apply UCT
            return (SolverNode) this.uct.getNode(node);
        }
        
        @Override
        public String toString() {
            return "LGRUCT";
        }
    }
}
